package pro.cherish.smartfile.test.file;

import cn.hutool.core.collection.CollUtil;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.MediaType;
import org.springframework.http.codec.multipart.FilePart;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ServerWebExchange;
import pro.cherish.smartfile.common.response.Result;
import pro.cherish.smartfile.module.entity.FileUploadDTO;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.io.*;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * @author 创建人：lys
 * @version 版本号：V1.0
 * <p>
 * ***************************修订记录************************************
 * 2020-12-01 lys 创建该类功能。
 * **********************************************************************
 * </p>
 * @ClassName 类名：IndexController
 * @Description 功能说明：
 * <p>
 *
 * </p>
 * **********************************************************************
 * @date 创建日期： 2020-12-01 lys
 */

@RestController
@RequestMapping("/api/xx/file")
@Api(tags = "xx文件")
@Slf4j
public class UploadController {

    private static final int BLOCK_SIZE = 1024;
    private static Map<String, ArrayList<Path>> map = new ConcurrentHashMap<>();


    @CrossOrigin(origins = "*", maxAge = 3600)
    @GetMapping(value = "/remove")
    public Mono<Result> remove(@RequestParam String name) throws IOException {
        map.remove(name);
        return Mono.just(Result.ok("清空完成"));
    }

    @CrossOrigin(origins = "*", maxAge = 3600)
    @GetMapping(value = "/get")
    public Mono<Result> get(@RequestParam String name) throws IOException {
        File folder = new File("D:\\tmp\\cherishfile\\");
        File[] files = folder.listFiles();
        ArrayList<Path> paths = map.get(name);
        for (File file : files) {
            if (paths.stream().filter(item -> item.toString().equals(file.toPath().toString())).collect(Collectors.toList()).size() == 0) {
                log.error("丢了{}", file.toPath());
            }
        }


        return Mono.just(Result.ok(paths));
    }

    @CrossOrigin(origins = "*", maxAge = 3600)
    @GetMapping(value = "/merge")
    public Mono<Result> merge(@RequestParam String name) throws IOException {
//        ArrayList<Path> paths = map.get(name);
//        ArrayList<File> failChunkFiles = new ArrayList<>();
//        File outFile = new File("D:\\tmp\\cherishfile\\my.jpg");
//        if (!outFile.exists()) {
//            outFile.createNewFile();
//        }
//        try (FileChannel outChannel = new FileOutputStream(outFile).getChannel()) {
//            for (Path path : paths) {
//                File chunkFile = new File(path.toString());
//                try (FileChannel inChannel = new FileInputStream(chunkFile).getChannel()) {
//                    inChannel.transferTo(0, inChannel.size(), outChannel);
//                } catch (FileNotFoundException ex) {
//                    log.error(ex.toString());
//                    failChunkFiles.add(chunkFile);
//                }
//            }
//        } catch (Exception e) {
//            log.error(e.toString());
//        }


        File folder = new File("D:\\tmp\\cherishfile\\");
        File outFile = new File("D:\\tmp\\cherishfile\\" + name);
        File[] files = folder.listFiles();
        try {
            if (!outFile.exists()) {
                outFile.createNewFile();
            }
            List<File> collect = Arrays.stream(files).sorted(new Comparator<File>() {
                @Override
                public int compare(File o1, File o2) {
                    String[] split = o1.toPath().getFileName().toString().split("\\.");
                    int i1 = Integer.parseInt(split[0]);
                    String[] split2 = o2.toPath().getFileName().toString().split("\\.");
                    int i2 = Integer.parseInt(split2[0]);
                    return i1 - i2;
                }
            }).collect(Collectors.toList());

            FileOutputStream fileOutputStream = new FileOutputStream(outFile);
            FileChannel mFileChannel = fileOutputStream.getChannel();
            FileChannel inFileChannel;
            for (File file : collect) {
                inFileChannel = new FileInputStream(file).getChannel();
                inFileChannel.transferTo(0, inFileChannel.size(), mFileChannel);
                inFileChannel.close();
            }
            mFileChannel.close();
            fileOutputStream.close();
        } catch (Exception e) {
            log.error(e.toString());
        }


//        ArrayList<Path> paths = map.get(name);
//
//        File outFile = new File("D:\\tmp\\cherishfile\\xxx.jpg");
//        if (!outFile.exists()) {
//            outFile.createNewFile();
//        }
//        FileChannel mFileChannel = new FileOutputStream(outFile).getChannel();
//        FileChannel inFileChannel;
//        List<Path> collect = paths.stream().sorted(new Comparator<Path>() {
//            @Override
//            public int compare(Path o1, Path o2) {
//                String[] split = o1.getFileName().toString().split("\\.");
//                int i1 = Integer.parseInt(split[0]);
//                String[] split2 = o2.getFileName().toString().split("\\.");
//                int i2 = Integer.parseInt(split2[0]);
//                return i1 - i2;
//            }
//        }).collect(Collectors.toList());
//        for (Path path : collect) {
//            File infile = new File(path.toString());
//            inFileChannel = new FileInputStream(infile).getChannel();
//            inFileChannel.transferTo(0, inFileChannel.size(), mFileChannel);
//            inFileChannel.close();
//        }
//        mFileChannel.close();
        return Mono.just(Result.ok("合并成功"));
//        if (CollUtil.isNotEmpty(failChunkFiles)) {
//            Set<String> collect = failChunkFiles.stream().map(item -> item.getName()).collect(Collectors.toSet());
//            log.error(collect.toString());
//            return Mono.just(Result.ok("合并失败，存在失败的文件块"));
//        } else {
//            return Mono.just(Result.ok("合并成功"));
//        }
    }

//        filePart.content().subscribe(buffer -> {
//            byte[] bytes = new byte[buffer.readableByteCount()];
//            buffer.read(bytes);
//            DataBufferUtils.release(buffer);
//            int fileByteLength = bytes.length;
//            if (fileByteLength > 0) {
//                if (fileByteLength == BLOCK_SIZE) {
//                    new Thread(() -> {
//                        try {
//                            Integer chunk = fileUploadDTO.getChunk();
//                            Integer chunks = fileUploadDTO.getChunks();
//                            Long size = fileUploadDTO.getSize();
//                            String fileName = fileUploadDTO.getName();
//                            String lastModifiedDate = fileUploadDTO.getLastModifiedDate();
//                            log.error("chunk={} chunks={} size={} fileByteLength={}", chunk, chunks, size, fileByteLength);
//                            int beginIndex = (chunk + 1 - 1) * BLOCK_SIZE;
//                            int endIndex = (chunk + 1 - 1) * BLOCK_SIZE + fileByteLength;
//                            log.error("beginIndex={},endIndex={}", beginIndex, endIndex);
//                            ByteArrayInputStream is = new ByteArrayInputStream(bytes);
//                            new FileInputStream().ge
//                            File file = new File("D:\\tmp\\cherishfile\\" + chunk + ".data");
//                            file.createNewFile();
////                        File tempFile = FileUtil.createTempFile("chunk-", chunk + ".data", file, true);
//                            FileChannel fileChannel = FileChannel.open(file.toPath(), StandardOpenOption.WRITE);
//                            try (FileChannel inChannel = new ByteInputStream(bytes, by).getChannel()) {
//                                inChannel.transferTo(0, inChannel.size(), fileChannel);
//                            } catch (FileNotFoundException ex) {
//                                log.error(ex.toString());
//                                throw ex;
//                            }
//                        } catch (Exception e) {
//                            log.error(e.toString());
//                        }
//                    }).start();
//                } else {
//                    try {
//                        File file = new File("D:\\tmp\\cherishfile\\" + 1 + ".data");
//                        file.createNewFile();
//                        FileChannel outChannel = FileChannel.open(file.toPath(), StandardOpenOption.WRITE);
//                        try (FileChannel inChannel = new FileInputStream(file).getChannel()) {
//                            inChannel.transferTo(0, inChannel.size(), outChannel);
//                        } catch (FileNotFoundException ex) {
//                            log.error(ex.toString());
//                            throw ex;
//                        }
//                    } catch (Exception e) {
//                        log.error(e.toString());
//                    }
//                }
//            }
//        });

//        Long start = System.currentTimeMillis();
//        UploadTask oldTask = Cache.getById(uploadId);
//        if (ObjectUtil.isNotNull(oldTask)) {
//            // 续传
//            return null;
//        }
//        Flux<DataBuffer> content = filePart.content();
//        HttpHeaders headers = filePart.headers();
//        List<HttpRange> range = headers.getRange();
//        content.subscribe(buffer -> {
//            byte[] bytes = new byte[buffer.readableByteCount()];
//            buffer.read(bytes);
//            DataBufferUtils.release(buffer);
//            try {
//                int length = bytes.length;
//                String bodyString = new String(bytes, "utf-8");
//                System.out.println(bodyString);
//
//                File targetFile = FileUtil.createTempFile(new File("D:\\tmp\\"));
//                FileUtil.writeBytes(bytes, targetFile);
//                long currentTimeMillis = System.currentTimeMillis();
//                long useTime = currentTimeMillis - start;
//
//                Map size = Utils.getSize(Long.valueOf(length + ""));
//                String unit = size.get("unit").toString();
//                String resultSize = size.get("resultSize").toString();
//                double second = Double.valueOf(useTime) / 1000D;
//                BigDecimal bg = new BigDecimal((Double.valueOf(resultSize) / second));
//                double result = bg.setScale(4, BigDecimal.ROUND_HALF_UP).doubleValue();
//                //推送上传进度
//                Map map = new HashMap();
//                //任务id
//                map.put("uploadId", uploadId);
//                //传输长度
//                long currentLength = 0L;
//                UploadTask uploadTask = new UploadTask();
//                uploadTask.setTaskId(StrUtil.uuid());
//                uploadTask.setStartCount(1);
//                if (ObjectUtil.isNotNull(uploadTask.getCurrentLength())) {
//                    uploadTask.setCurrentLength(length + currentLength);
//                    uploadTask.setCurrentLengthDesc(resultSize + "/" + unit);
//                }
//                uploadTask.setSpeed(result);
//                Cache.save(uploadTask);
//            } catch (UnsupportedEncodingException e) {
//                e.printStackTrace();
//            }
//        });

    @CrossOrigin(origins = "*", maxAge = 3600)
    @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public Mono<Result> upload(FileUploadDTO fileUploadDTO, @RequestPart("file") FilePart filePart) {
        String name = fileUploadDTO.getName();
        ArrayList<Path> files = map.get(name);
        if (CollUtil.isEmpty(files)) {
            files = new ArrayList<>();
        }
        ArrayList<Path> finalFiles = files;
        filePart.content().subscribe(buffer -> {
            byte[] bytes = new byte[buffer.readableByteCount()];
            Flux<DataBuffer> content = filePart.content();
            int fileByteLength = bytes.length;
            Integer chunk = fileUploadDTO.getChunk();
            try {
                if (fileByteLength > 0) {
                    if (fileByteLength == BLOCK_SIZE) {
                        new Thread(() -> {
                            try {
                                File file = new File("D:\\tmp\\cherishfile\\" + chunk + ".data");
                                file.createNewFile();
                                Path path = file.toPath();
                                AsynchronousFileChannel channel;
                                try {
                                    channel = AsynchronousFileChannel.open(path, StandardOpenOption.WRITE);
                                    DataBufferUtils.write(content, channel, 0)
                                            .doOnComplete(() -> {
                                                log.info("block={}上传完毕, byte length ={}", chunk, fileByteLength);
                                                finalFiles.add(path);
                                                map.put(name, finalFiles);
                                            }).subscribe();
                                } catch (Exception e) {
                                    log.error(e.toString());
                                }
                            } catch (IOException e) {
                                e.printStackTrace();
                            }

                        }).start();
                    } else {
                        File file = new File("D:\\tmp\\cherishfile\\" + chunk + ".data");
                        file.createNewFile();
                        Path path = file.toPath();
                        AsynchronousFileChannel channel = AsynchronousFileChannel.open(path, StandardOpenOption.WRITE);

                        DataBufferUtils.write(content, channel, 0)
                                .doOnComplete(() -> {
                                    log.info("完整文件（未分块）传输完毕，{} byte length ={}", path.toAbsolutePath(), fileByteLength);
                                    log.info("name={}", name);
                                    finalFiles.add(path);
                                    map.put(name, finalFiles);
                                })
                                .subscribe();
                    }
                } else {
                    log.error("文件大小异常，不要上传");
                    throw new RuntimeException("文件大小异常，不要上传");
                }
            } catch (Exception e) {
                log.error(e.toString());
            }
        });
        return Mono.just(Result.ok());
    }

    @CrossOrigin(origins = "*", maxAge = 3600)
    @PostMapping(value = "/upload2", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public Mono<String> requestBodyFlux(@RequestPart("file") FilePart filePart,
                                        String uploadId, ServerWebExchange serverWebExchange) throws IOException {
        filePart.content().subscribe(buffer -> {
            byte[] bytes = new byte[buffer.readableByteCount()];
            buffer.read(bytes);
            DataBufferUtils.release(buffer);
            int fileByteLength = bytes.length;
            if (fileByteLength > 0) {
                serverWebExchange.getMultipartData().subscribe(stringPartMultiValueMap -> {
                    //log.info(stringPartMultiValueMap.toString());
                    new Thread(() -> {
                        try {
                            Integer chunk = Integer.parseInt(new String(stringPartMultiValueMap.getFirst("chunk").content().blockLast().asByteBuffer().array(), "utf-8"));
                            Integer chunks = Integer.parseInt(new String(stringPartMultiValueMap.getFirst("chunks").content().blockLast().asByteBuffer().array(), "utf-8"));
                            Long size = Long.parseLong(new String(stringPartMultiValueMap.getFirst("size").content().blockLast().asByteBuffer().array(), "utf-8"));
                            String id = new String(stringPartMultiValueMap.getFirst("id").content().blockLast().asByteBuffer().array(), "utf-8");
                            String name = new String(stringPartMultiValueMap.getFirst("name").content().blockLast().asByteBuffer().array(), "utf-8");
                            String lastModifiedDate = new String(stringPartMultiValueMap.getFirst("lastModifiedDate").content().blockLast().asByteBuffer().array(), "utf-8");
                            log.error("chunk={} chunks={} size={} fileByteLength={}", chunk, chunks, size, fileByteLength);
                            int beginIndex = (chunk + 1 - 1) * BLOCK_SIZE;
                            int endIndex = (chunk + 1 - 1) * BLOCK_SIZE + fileByteLength;
                            log.error("beginIndex={},endIndex={}", beginIndex, endIndex);
//                            File targetFile = FileUtil.file(new File("D:\\tmp\\test.txt"));
//                            FileUtil.writeBytes(bytes, targetFile,beginIndex,endIndex,true);

                            ByteArrayInputStream is = new ByteArrayInputStream(bytes);
                            RandomAccessFile oSavedFile = new RandomAccessFile("D:\\tmp\\test.jpg", "rw");
                            // 定位文件指针到 nPos 位置
                            oSavedFile.seek(beginIndex);
                            try {
                                int bytesWritten = 0;
                                int byteCount = 0;
                                byte[] b = new byte[1024];
                                while ((byteCount = is.read(b)) != -1) {
                                    oSavedFile.write(b, bytesWritten, byteCount);
                                    bytesWritten += byteCount;
                                }
                            } catch (Exception e) {
                                log.error(e.toString());
                            } finally {
                                is.close();
                            }
                        } catch (Exception e) {
                            log.error(e.toString());
                        }
                    }).start();
                });
            }
        });

//        Long start = System.currentTimeMillis();
//        UploadTask oldTask = Cache.getById(uploadId);
//        if (ObjectUtil.isNotNull(oldTask)) {
//            // 续传
//            return null;
//        }
//        Flux<DataBuffer> content = filePart.content();
//        HttpHeaders headers = filePart.headers();
//        List<HttpRange> range = headers.getRange();
//        content.subscribe(buffer -> {
//            byte[] bytes = new byte[buffer.readableByteCount()];
//            buffer.read(bytes);
//            DataBufferUtils.release(buffer);
//            try {
//                int length = bytes.length;
//                String bodyString = new String(bytes, "utf-8");
//                System.out.println(bodyString);
//
//                File targetFile = FileUtil.createTempFile(new File("D:\\tmp\\"));
//                FileUtil.writeBytes(bytes, targetFile);
//                long currentTimeMillis = System.currentTimeMillis();
//                long useTime = currentTimeMillis - start;
//
//                Map size = Utils.getSize(Long.valueOf(length + ""));
//                String unit = size.get("unit").toString();
//                String resultSize = size.get("resultSize").toString();
//                double second = Double.valueOf(useTime) / 1000D;
//                BigDecimal bg = new BigDecimal((Double.valueOf(resultSize) / second));
//                double result = bg.setScale(4, BigDecimal.ROUND_HALF_UP).doubleValue();
//                //推送上传进度
//                Map map = new HashMap();
//                //任务id
//                map.put("uploadId", uploadId);
//                //传输长度
//                long currentLength = 0L;
//                UploadTask uploadTask = new UploadTask();
//                uploadTask.setTaskId(StrUtil.uuid());
//                uploadTask.setStartCount(1);
//                if (ObjectUtil.isNotNull(uploadTask.getCurrentLength())) {
//                    uploadTask.setCurrentLength(length + currentLength);
//                    uploadTask.setCurrentLengthDesc(resultSize + "/" + unit);
//                }
//                uploadTask.setSpeed(result);
//                Cache.save(uploadTask);
//            } catch (UnsupportedEncodingException e) {
//                e.printStackTrace();
//            }
//        });
        return Mono.just(filePart.filename());
    }

}
